{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Single-Qubit Gate\n",
    "\n",
    "\n",
    "*Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved.*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Outline\n",
    "This tutorial introduces how to generate and optimize pulses for single-qubit gates by Quanlse. The outline of this tutorial is as follows:\n",
    "- Introduction\n",
    "- Preparation\n",
    "- Construct Hamiltonian\n",
    "- Pulse optimization for Hadamard gate\n",
    "- Pulse optimization for X gate\n",
    "- Pulse optimization for Z gate\n",
    "- Pulse optimization for arbitrary single-qubit gate\n",
    "- Summary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "\n",
    "  In quantum computing, we call an action upon qubit(s) a **quantum gate**. In superconducting quantum circuits, a quantum gate is implemented by applying external microwave pulses and magnetic flux. A single-qubit gate, the focus of this section, can be written as the symbol $U$, which is a $2\\times2$ unitary matrix in linear algebra. As shown in the graph below, a single-qubit gate can also be visualized by a vector ($|\\psi\\rangle$) transformation on what we call a Bloch Sphere. In particular, $|\\psi\\rangle=\\cos(\\theta/2)|0\\rangle+e^{i\\phi}\\sin(\\theta/2)|1\\rangle$ represents the superposition of two quantum states, $|0\\rangle$ and $|1\\rangle$. Via a single-qubit gate, we can transform a quantum state on the Bloch Sphere \\[1\\]. \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![bloch sphere](figures/sphere.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following are six commonly used single-qubit gates as well as their operators and matrix representations:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Gate   |      Operation on Bloch Sphere      |  Operator | Matrix Form | \n",
    "|----------|:-------------:|:-:|:-:|\n",
    "| $X$ |  Rotate $\\pi$ about the $x$ axis | $\\hat{\\sigma}_x$ | $\\left(\\begin{array}{cc} 0 & 1\\\\1 & 0\\end{array}\\right)$ |\n",
    "| $Y$ |  Rotate $\\pi$ about the $y$ axis | $\\hat{\\sigma}_y$ |$\\left(\\begin{array}{cc} 0 & -i\\\\i & 0\\end{array}\\right)$ |\n",
    "| $Z$ |  Rotate $\\pi$ about the $z$ axis | $\\hat{\\sigma}_z$ |$\\left(\\begin{array}{cc} 1 & 0\\\\0 & -1\\end{array}\\right)$ |\n",
    "| $S$ |  Rotate $\\pi\\over 2$ about the $z$ axis |  |$\\left(\\begin{array}{cc} 1 & 0\\\\0 & e^{i\\frac{\\pi}{2}}\\end{array}\\right)$ |\n",
    "| $T$ |  Rotate $\\pi\\over 4$ about the $z$ axis |  |$\\left(\\begin{array}{cc} 1 & 0\\\\0 & e^{i\\frac{\\pi}{4}}\\end{array}\\right)$ |\n",
    "| $H$ | First rotate $\\pi$ about the $x$ axis, then rotate $\\pi\\over 2$ about the $z$ axis |  |$\\frac{1}{\\sqrt{2}}\\left(\\begin{array}{cc} 1 & 1\\\\1 & -1\\end{array}\\right)$ |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Single-qubit gate in Quanlse**\n",
    "\n",
    "Please note that in Quanlse, any arbitrary operation on a single qubit is implemented by the following equation (this representation has a global phase in front and no $R_x$ component):\n",
    "$$\n",
    "U(\\theta, \\phi, \\lambda) = e^{i(\\phi/2+\\lambda/2)} R_z(\\phi) R_y(\\theta) R_z(\\lambda) =\n",
    "\\begin{bmatrix} \n",
    "    \\cos(\\theta/2) & - e^{i\\lambda} \\sin(\\theta/2) \\\\\n",
    "    e^{i\\phi} \\sin(\\theta/2) & e^{i(\\phi + \\lambda)} \\cos(\\theta/2)\n",
    "\\end{bmatrix} ,\n",
    "$$\n",
    "where the $e^{i(\\phi/2+\\lambda/2)}$ is the global phase of the transformation.\n",
    "\n",
    "Please refer to our [API documentation](https://quanlse.baidu.com/api/) for more information regarding other types of gates Quanlse supports."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To get the physical intuition of the implementation of single-qubit gates, we will now introduce the two means of implementing single-qubit gates on superconducting qubit:\n",
    "- **Microwave control** uses capacitive coupling between a resonator and the superconducting qubit (X, Y channel).\n",
    "- **Frequency tuning** uses a local magnetic field (Z channel) for flux-tunable qubits.\n",
    "\n",
    "The graph below delineates the X/Y/Z channels of a superconducting qubit:\n",
    "\n",
    "![X/Y/Z controls for single superconducting qubit](figures/hardware_qubit_control.png)\n",
    "\n",
    "\n",
    "**Experimental microwave pulse implementation**\n",
    "\n",
    "To experimentally implement microwave control (X, Y control), we need a local oscillator (LO) to produce a high frequency ($\\omega_{\\rm LO}$) sinusoidal component combined with a low frequency ($\\omega_{\\rm AWG}$) arbitrary waveform generator (AWG) to produce a pulse envelope in the form of a Gaussian or tangential function, etc., such that $\\omega_{d}=\\omega_{\\rm LO}\\pm\\omega_{\\rm AWG}$. \n",
    "\n",
    "**Experimental flux pulse implementation**\n",
    "\n",
    "The conventional way of implementing flux control (Z control) is to replace the single Josephson junction with a DC-SQUID, which is a loop consisting of two Josephson junctions. Qubit frequency can be tuned by applying an external magnetic flux perpendicular to the loop."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preparation\n",
    "After you have successfully installed Quanlse, you could run the Quanlse program below following this tutorial. To run this particular tutorial, you would need to import the following packages from Quanlse and other commonly-used Python libraries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import numpy and math\n",
    "from numpy import round\n",
    "from math import pi\n",
    "\n",
    "# Import the Hamiltonian module\n",
    "from Quanlse.QHamiltonian import QHamiltonian as QHam\n",
    "\n",
    "# Import simulator interface on Quanlse Cloud Service\n",
    "from Quanlse.remoteOptimizer import remoteOptimize1Qubit\n",
    "\n",
    "# Import related packages\n",
    "from Quanlse.Utils.Functions import project\n",
    "from Quanlse.QOperator import duff\n",
    "from Quanlse.QOperation import FixedGate"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Construct Hamiltonian\n",
    "\n",
    "Now, we switch our focus to implementing a single-qubit gate using Quanlse. In this example, we will model a system formed by a three-level transmon. Ideally, the non-equidistant energy levels in a transmon allow qubit states to be individually addressed through a frequency-selective driving field. However, the fact that the transmon qubit is weakly anharmonic suggests that the finite bandwidth of the driving field can lead to transitions out of the computational states. This effect is known as **leakage**. In our model, leakage into state $|2\\rangle$ - the first level above the computational space - is taken into account by treating the superconducting qubit as a simplified three-level system. The Hamiltonian in the rotating frame can be written as \\[2\\]:\n",
    "\n",
    " $$\n",
    " \\hat{H}=\\alpha_q\\lvert2\\rangle\\langle 2\\lvert+\\frac{\\Omega^x(t)}{2}\\left[ \\hat{a}^\\dagger + \\hat{a} \\right] + \\frac{\\Omega^y(t)}{2} i \\left[\\hat{a}^\\dagger - \\hat{a}\\right]+\\Omega^z(t)\\hat{a}^{\\dagger}\\hat{a},\n",
    " $$\n",
    "\n",
    "where $\\alpha_q$ is the anharmonicity; $\\Omega^x(t)$ is the amplitude of the driving pulse in the X channel; $\\Omega^y(t)$ is the amplitude of the driving pulse in the Y channel; and $\\Omega^z(t)$ is the amplitude of the flux pulses in the Z channel. The two ladder operators are respectively $\\hat{a}^\\dagger = |1\\rangle\\langle 0| + \\sqrt{2}|2\\rangle\\langle 1|$ and $\\hat{a} = |0\\rangle\\langle 1| + \\sqrt{2}|1\\rangle\\langle 2|$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quanlse can be used to realize arbitrary single-qubit gates. While Quanlse supports various waveforms' definitions. Here, we take the Gaussian pulse function as an example. The Gaussian pulse function takes the form:\n",
    "\n",
    "$$\n",
    "A^{x}(t)=A^{x} e^{-((t-\\tau^{x})/2\\sigma^{x})^2}, \n",
    "$$\n",
    "\n",
    "$$\n",
    "A^{y}(t)=A^{y} e^{-((t-\\tau^{y})/2\\sigma^{y})^2} .\n",
    "$$\n",
    "\n",
    "In these equations above, $A^{x}, A^{y}, \\tau^{x}, \\tau^{y}, \\sigma^{x}, \\sigma^{y}$ are the parameters to be optimized.\n",
    "Unlike microwave control, the flux channel's input takes the form of a square wave, i.e. $\n",
    "A^{z}(t) = A^{z}$, where $A^{z}$ is the parameter to be optimized."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we need to construct the above Hamiltonian using Quanlse. We start by defining some of the basic parameters needed for constructing a Hamiltonian object: the sampling period, the number of qubits in the system as well as the system's energy levels to consider:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Sampling period\n",
    "dt = 0.2\n",
    "\n",
    "# Number of qubits\n",
    "qubits = 1\n",
    "\n",
    "# System energy level\n",
    "level = 3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then, we define the anharmonicity of the qubit:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define anharmonicity\n",
    "qubitArgs = {\n",
    "    \"qubit_anharm\": - 0.33 * (2 * pi),  # Anharmonicity of the qubit\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We create the Hamiltonian object using the initilizer `Qham()`. When initializing, we specify the qubit number, system's energy level number and the sampling period of the AWG(arbitrary wave generator). Then, the users would need to add the anharmonicity term using the function `addDrift()` which takes three arguments: the corresponding operator, the qubit index, and the amplitude of the anharmonicity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the Hamiltonian.\n",
    "ham = QHam(qubits, level, dt)\n",
    "\n",
    "# Add the drift term(s).\n",
    "ham.addDrift(duff, 0, coef=qubitArgs[\"qubit_anharm\"] / 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pulse optimization for Hadamard gate"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With the system Hamiltonian created, we can now generate and optimize the pulses to a selected single-qubit gate with desired infidelity (we choose the Hadamard gate as an example) using the following function: `remoteOptimize1Qubit()`. The optimization usually takes a long time to process on local devices, however, we provide a cloud service that could speed up this process significantly. To use Quanlse Cloud Service, the users need to acquire a token from http://quantum-hub.baidu.com and use the following command to submit the job onto Quanlse's server. For this specific example, we can write:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import Define class and set the token\n",
    "# Please visit http://quantum-hub.baidu.com\n",
    "from Quanlse import Define\n",
    "Define.hubToken = ''\n",
    "\n",
    "# Run the optimization\n",
    "gateJob, infidelity = remoteOptimize1Qubit(ham, FixedGate.H.getMatrix(), depth=4, targetInfid=0.0001)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the optimization above, the gate infidelity for performance assessment is defined as ${\\rm infid} = 1 - \\frac{1}{d}\\left|{\\rm Tr}[U^\\dagger_{\\rm goal}P(U)]\\right|$, where $U_{\\rm goal}$ is the unitary matrix of the target single-qubit gate and $d$ is the dimension of $U_{\\rm goal}$; $U$ is the unitary evolution of the system. Note that $P(U)$ in particular describes the evolution projected to the computational subspace.\n",
    "\n",
    "The function `remoteOptimize1Qubit()` takes in a Hamiltonian object (`ham`), a target unitary matrix (`FixedGate.H.getMatrix()`), the maximum pulse circuit's depth (also maximum pulse number), and a target infidelity. While this demo generates a decent fidelity for the Hadamard gate, we encourage the users to try varying these parameters to get the optimal result.\n",
    "\n",
    "The `plot()` function allows us to visualize the pulse generated. We can also obtain the projected evolution:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Print infidelity and the waveforms\n",
    "print(f\"minimum infidelity: {infidelity}\")\n",
    "gateJob.plot(dark='True')\n",
    "\n",
    "# Print the evolution process.\n",
    "result = ham.simulate(job=gateJob)\n",
    "projectedEvolution = project(result.result[0][\"unitary\"], qubits, level, 2)\n",
    "print(\"Projected evolution:\\n\", round(projectedEvolution, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`plotWaves()`'s function arguments include a Hamiltonian object (`ham`), the channels to print(`[\"q0-ctrlx\", \"q0-ctrly\", \"q0-ctrlz\"]`) and an optional bool parameter `dark`, which enables a dark-themed mode. Moreover, the user can use the `color` parameter to specify colors for individual pulses (the colors will repeat if there are more pulses than colors). Apart from the pulse visualization, we can also obtain the projected evolution shown above.\n",
    "\n",
    "The following are demonstrations for the X and Z gates, using the exact same system Hamiltonian as above."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pulse optimization for X gate"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following code generates and optimizes the pulses for an X gate. Here, we specify the pulse numbers on the X and Y channels by setting the parameter: `xyzPulses=[1, 1, 0]`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# Run the optimization\n",
    "gateJob, infidelity = remoteOptimize1Qubit(ham, FixedGate.X.getMatrix(), depth=4, targetInfid=0.0001)\n",
    "\n",
    "# Print infidelity and the waveforms\n",
    "print(f\"minimum infidelity: {infidelity}\")\n",
    "gateJob.plot(dark='True')\n",
    "\n",
    "# Print the evolution process.\n",
    "result = ham.simulate(job=gateJob)\n",
    "projectedEvolution = project(result.result[0][\"unitary\"], qubits, level, 2)\n",
    "print(\"Projected evolution:\\n\", round(projectedEvolution, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pulse optimization for Z gate"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following code generates and optimizes the pulses for a Z gate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# Run the optimization\n",
    "gateJob, infidelity = remoteOptimize1Qubit(ham, FixedGate.Z.getMatrix(), depth=4, targetInfid=0.0001)\n",
    "\n",
    "# Print infidelity and the waveforms\n",
    "print(f\"minimum infidelity: {infidelity}\")\n",
    "gateJob.plot(dark='True')\n",
    "\n",
    "# Print the evolution process.\n",
    "result = ham.simulate(job=gateJob)\n",
    "projectedEvolution = project(result.result[0][\"unitary\"], qubits, level, 2)\n",
    "print(\"Projected evolution:\\n\", round(projectedEvolution, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pulse optimization for arbitrary single-qubit gate"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following code generates and optimizes the pulses for an arbitrary gate, `U(theta=-1.231, phi=1.231, lamda=-1.231)`. Note that the user would need to import `U` from `RotationGate`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from Quanlse.QOperation.RotationGate import U\n",
    "\n",
    "# Define a U3 gate\n",
    "aGate = U(theta=-1.231, phi=1.231, lamda=-1.231)\n",
    "\n",
    "# Run the optimization\n",
    "gateJob, infidelity = remoteOptimize1Qubit(ham, aGate.getMatrix(), depth=4, targetInfid=0.0001)\n",
    "\n",
    "# Print infidelity and the waveforms\n",
    "print(f\"minimum infidelity: {infidelity}\")\n",
    "gateJob.plot(dark='True')\n",
    "\n",
    "# Print the evolution process.\n",
    "result = ham.simulate(job=gateJob)\n",
    "projectedEvolution = project(result.result[0][\"unitary\"], qubits, level, 2)\n",
    "print(\"Projected evolution:\\n\", round(projectedEvolution, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Summary\n",
    "\n",
    "This tutorial introduces the complete process of generating and optimizing pulses for any single-qubit gate using Quanlse. The users could follow this link [tutorial-single-qubit-gate.ipynb](https://github.com/baidu/Quanlse/blob/main/Tutorial/EN/tutorial-single-qubit.ipynb) to the GitHub page of this Jupyter Notebook document and obtain the relevant code for themselves. The users are encouraged to try parameter values different from this tutorial to obtain the optimal result."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## References\n",
    "\\[1\\] [Nielsen, Michael A., and Isaac L. Chuang. Quantum Computation and Quantum Information: 10th Anniversary Edition. Cambridge University Press, 2010.](https://doi.org/10.1017/CBO9780511976667)\n",
    "\n",
    "\\[2\\] [Wilhelm, Frank K., et al. \"An introduction into optimal control for quantum technologies.\" *arXiv preprint arXiv:2003.10132* (2020).](https://arxiv.org/abs/2003.10132)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
